/*******************************************************************************
  * Copyright (c) 2013 E.W.Clay.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *******************************************************************************/ 

#pragma once

#include "utility/sharedptr.h"

namespace Serialisation
{

template <typename Archive, typename T>
void Deserialise(Archive& ar, T& t)
{
	Serialise(ar, t);
}

enum class SerialisationType
{
	Object,
	Array,
	Enum,
	String,
};

template <typename T>
struct SerialisationInfo
{
	const static SerialisationType type = SerialisationType::Object;
};

template <typename T>
struct Unquoted_t
{
	Unquoted_t(const T& v) : value(const_cast<T&>(v)) {}

	T& value;
};

template <typename T>
Unquoted_t<T> Unquoted(const T& value)
{
	return Unquoted_t<T>(value);
}

template <typename T>
struct EnumTypes 
{
	T type; 
	const char* name;
};

template <typename Serialiser>
class Handler
{
public:
	virtual void Serialise(Serialiser& ar, void* p) = 0;
	virtual void DeserialiseObject(typename Serialiser::Deserialiser& ar, void*& p) = 0;
	virtual void DeserialiseDirect(typename Serialiser::Deserialiser& ar, void*& p) = 0;
	virtual Ut::DestroyFunctionPtr GetDestructor() = 0;
};

template <typename Serialiser, typename T>
class Handler_t : public Handler<Serialiser>
{
public:
	virtual void Serialise(Serialiser& ar, void* p)
	{
		ar.Serialise("Object", *(T*) p);
	}

	virtual void DeserialiseObject(typename Serialiser::Deserialiser& ar, void*& p)
	{
		p = new T;
		ar.Serialise("Object", *(T*) p);
	}

	virtual void DeserialiseDirect(typename Serialiser::Deserialiser& ar, void*& p)
	{
		p = new T;
		ar.DeserialiseDirect(*(T*) p);
	}

	virtual Ut::DestroyFunctionPtr GetDestructor()
	{
		return &Ut::Destroy<T>;
	}
};

template <typename Serialiser>
class HandlerMap
{
public:
	static void Register(const char* typeID, Handler<Serialiser>* pHandler)
	{
		Assert(s_handlerMap.find(typeID) == s_handlerMap.end());

		s_handlerMap[typeID] = Ut::MakeShared(pHandler);
	}

	static Handler<Serialiser>* GetHandler(const char* typeID)
	{
		return (Handler<Serialiser>*) s_handlerMap[typeID];
	}

private:
	static std::map<std::string, Ut::SharedPtr<Handler<Serialiser>>> s_handlerMap;
};

template <typename Archive>
class Serialiser
{
public:
	template <typename T>
	static bool Save(const char* filename, const char* objectName, T& t)
	{
		std::ofstream file;
		file.open(filename);

		if (file.is_open())
		{
			Archive s(file);
			s.Serialise(objectName, t);

			return true;
		}

		return false;
	}

	bool Register(void* p)
	{
		if (m_pointers.find(p) != m_pointers.end())
			return false;

		m_pointers.insert(p);

		return true;
	}

private:
	std::set<void*> m_pointers;
};

template <typename Archive>
class Deserialiser
{
public:
	template <typename T>
	static bool Load(const char* filename, const char* objectName, T& t)
	{
		std::ifstream file;
		file.open(filename);

		if (file.is_open())
		{
			Archive d(file);
			d.Serialise(objectName, t);

			return true;
		}
	
		return false;
	}

	Ut::ReferenceCountedPointer* Find(uint64 id)
	{
		std::map<uint64, Ut::ReferenceCountedPointer*>::iterator it = m_pointers.find(id);

		if (it != m_pointers.end())
			return it->second;

		return 0;
	}

	void Register(uint64 id, Ut::ReferenceCountedPointer* p)
	{
		m_pointers[id] = p;
	}

private:
	std::map<uint64, Ut::ReferenceCountedPointer*> m_pointers;
};

}

#define SERIALISATION_DECL_GUID(name)	static const char* GetSerialisationGuid_Static() {return #name;} \
										virtual const char* GetSerialisationGuid() const {return #name;} \
										template <typename Archive> friend void Serialise(Archive& ar, name& t);

#define SERIALISATION_DECL(name)		template <typename Archive> friend void Serialise(Archive& ar, name& t);

#define SERIALISATION_REGISTER(serialiser, name)	Serialisation::HandlerMap<serialiser>::Register(name::GetSerialisationGuid_Static(), new Serialisation::Handler_t<serialiser, name>);

#define SERIALISATION_IS_ENUM(name) 	template <> \
										struct SerialisationInfo <name> \
										{ \
											const static SerialisationType type = SerialisationType::Enum; \
										}; \

#define SERIALISATION_IS_ARRAY(name) 	template <> \
										struct SerialisationInfo <name> \
										{ \
											const static SerialisationType type = SerialisationType::Array; \
										}; \

#define SERIALISATION_ENUM_BEGIN(Type) Serialisation::EnumTypes<Type> types[] = {
#define SERIALISATION_ENUM_VALUE(Value, Name) {Value, #Name},
#define SERIALISATION_ENUM_END(Type, t) }; ar.SerialiseEnum(types, sizeof(types) / sizeof(Serialisation::EnumTypes<Type>), t);
